/* ssh2john utility, written in April of 2011 by Dhiru Kholia for GSoC.
 *
 * This software is Copyright © 2011, Dhiru Kholia <dhiru.kholia at gmail.com>,
 * and it is hereby released to the general public under the following terms:
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted.
 *
 * Usage:
 *
 * 1. Run ssh2john on private key file(s) as "ssh2john [key file(s)]". Output
 *    is written to standard output.
 * 2. Run JtR on the output generated by ssh2john as "john [output file]".
 *
 * Notes:
 *
 * Output Line Format: filename:$ssh2$hex-encoding-of-entire-file-contents*file-length
 *
 * TODO:
 *
 * 1. Support more formats of SSH private keys. */

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <string.h>

#include <openssl/ssl.h>
#include <openssl/dsa.h>
#include <openssl/rsa.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>

#include "misc.h"
#include "common.h"
#include "arch.h"
#include "params.h"

static void process_file(const char *filename)
{
	FILE *keyfile;
	int i, count;
	unsigned char buffer[LINE_BUFFER_SIZE];
	BIO *bp;
	char *nm = NULL, *header = NULL;
	unsigned char *data = NULL;
	EVP_CIPHER_INFO cipher;
	EVP_PKEY pk;
	long len;
	DSA *dsapkc = NULL;
	RSA *rsapkc = NULL;
	const char unsigned *dc;

	if (!(keyfile = fopen(filename, "rb"))) {
	    fprintf(stderr, "! %s : %s\n", filename, strerror(errno));
	    return;
	}
	/* verify input files using OpenSSL */
	bp = BIO_new(BIO_s_file());
	if(!bp) {
	    fprintf(stderr, "OpenSSL BIO allocation failure\n");
	    return;
	}
	if(!BIO_read_filename(bp, filename)) {
	    fprintf(stderr, "OpenSSL BIO_read_filename failure\n");
	    ERR_print_errors_fp(stderr);
	    return;
	}
	/* PEM_bytes_read_bio function in crypto/pem/pem_lib.c
	 * check_pem function in crypto/pem/pem_lib.c */
	for (;;) {
		if (!PEM_read_bio(bp, &nm, &header, &data, &len)) {
			if (ERR_GET_REASON(ERR_peek_error()) ==
			    PEM_R_NO_START_LINE) {
				/* ERR_print_errors_fp(stderr); */
	            fprintf(stderr, "! %s : %s\n", filename, "input keyfile validation failed");
				goto out;
			}
		}
		if(!nm) {
			fprintf(stderr, "! %s : %s\n", filename, "input keyfile validation failed");
			goto out;
		}
        /* only PEM encoded DSA and RSA private keys are supported. */
		if (!strcmp(nm, PEM_STRING_DSA)) {
			pk.save_type = EVP_PKEY_DSA;
			break;
		}
		if (!strcmp(nm, PEM_STRING_RSA)) {
			pk.save_type = EVP_PKEY_RSA;
			break;
		}
		OPENSSL_free(nm);
		OPENSSL_free(header);
		OPENSSL_free(data);
		BIO_free(bp);
	}
	if (!PEM_get_EVP_CIPHER_INFO(header, &cipher)) {
		ERR_print_errors_fp(stderr);
		return;
	}
	/* check if key has no password */
	dc = data;
	if (PEM_do_header(&cipher, data, &len, NULL, (char *) "")) {
		if (pk.save_type == EVP_PKEY_DSA) {
			if ((dsapkc = d2i_DSAPrivateKey(NULL, &dc, len)) != NULL) {
				fprintf(stderr, "%s has no password!\n", filename);
				DSA_free(dsapkc);
				goto out;
			}
		}
		else if (pk.save_type == EVP_PKEY_RSA) {
			if ((rsapkc = d2i_RSAPrivateKey(NULL, &dc, len)) != NULL) {
				fprintf(stderr, "%s has no password!\n", filename);
				RSA_free(rsapkc);
				goto out;
                        }
                }
        }
	/* key has been verified */
	count = fread(buffer, 1, LINE_BUFFER_SIZE, keyfile);
	printf("%s:$ssh2$", filename);
	for (i = 0; i < count; i++) {
	    printf("%c%c", itoa16[ARCH_INDEX(buffer[i] >> 4)],
	            itoa16[ARCH_INDEX(buffer[i] & 0x0f)]);
	}
	printf("*%d\n", count);
out:
	fclose(keyfile);
	if(bp)
		BIO_free(bp);
}

int ssh2john(int argc, char **argv)
{
	int i;

	/* OpenSSL init, cleanup part is left to OS */
	SSL_load_error_strings();
	SSL_library_init();
	OpenSSL_add_all_algorithms();

	if (argc < 2) {
		puts("Usage: ssh2john [key file(s)]");
		return 0;
	}

	for (i = 1; i < argc; i++)
	    process_file(argv[i]);

	return 0;
}
